﻿using DeHeng_Mes.Constant;
using DeHeng_Mes.Dao;
using DeHeng_Mes.Dao.PlcDao;
using DeHeng_Mes.Model;
using DeHeng_Mes.Model.Boss;
using DeHeng_Mes.Model.BossModel;
using DeHeng_Mes.Model.ConfigModel;
using DeHeng_Mes.Model.OrderSingleton;
using DeHeng_Mes.Model.PlcModel;
using DeHeng_Mes.Model.PlcModel.door_01;
using DeHeng_Mes.Model.Table;
using DeHeng_Mes.Model.ViewModel;
using DeHeng_Mes.PlcService;
using DeHeng_Mes.Service;
using DeHeng_Mes.Utils;
using Google.Protobuf.WellKnownTypes;
using NPOI.SS.Formula.Eval;
using NPOI.SS.Formula.Functions;
using Org.BouncyCastle.Utilities;
using S7.Net;
using SqlSugar;
using System;
using System.Buffers;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using static System.Windows.Forms.VisualStyles.VisualStyleElement;
using MappingTable = DeHeng_Mes.Utils.MappingTable;
using Type = System.Type;

namespace DeHeng_Mes.Thread
{
    public class LinePlcToMes
    {
        #region 全局成员
        OrderDao orderDao = new OrderDao();
        PlcDao plcDao = new PlcDao();
        EntranceService entranceService = new EntranceService();
        ExitService exitService = new ExitService();
        VerificationService verificationService = new VerificationService();
        SpotCheakService spotCheakService = new SpotCheakService();
        ErrorType errortype = new ErrorType();
        #endregion

        /// <summary>
        /// 处理读取完成的信号，将追溯码与订单、节拍等信息绑定
        /// </summary>
        /// <param name="types">数据类型字典</param>
        /// <param name="orderModel">工单模型</param>
        public void ProcessOkStatus(Dictionary<object, Type> types, PlcOrderConfigModel orderModel)
        {
            try
            {
                // 初始化状态字段
                string stationStatus = $"{orderModel.PlcName}-STATUS";

                // 获取该Plc工位状态数组信息
                Tuple<int, int> byteTuple = MappingTable.GetBytesOff(stationStatus);

                Tuple<int, int> returnByteTuple = MappingTable.GetReturnBytesOff(stationStatus);

                if (byteTuple == null && returnByteTuple == null)
                    return;

                // 读取状态数组数据
                byte[] bytes = orderModel.plc.ReadBytes(DataType.DataBlock, byteTuple.Item1, SystemConstant.ALL_STARTOFF, byteTuple.Item2);

                byte[] returnBytes = orderModel.plc.ReadBytes(DataType.DataBlock, returnByteTuple.Item1, SystemConstant.ALL_STARTOFF, returnByteTuple.Item2);

                // 获取状态字段对应偏移量
                Dictionary<string, Tuple<int, int>> fieldOffs = MappingTable.GetDicByName(stationStatus);

                // 获取二维码数据
                Dictionary<string, object> codeDic = plcDao.GetFieldValues(types, SystemConstant.CODE);

                string status = "";

                string station = "";

                string typeString = string.Empty;

                foreach (var fieldOff in fieldOffs)
                {
                    station = fieldOff.Key;

                    // 获取最新状态
                    status = bytes[fieldOff.Value.Item1].ToString();

                    // 获取旧状态
                    (string oldStatus, byte returnByte) = orderModel.StatusDic[station];

                    string operation = MappingTable.GetOperationId(station);

                    try
                    {
                        // 获取二维码列表
                        string code = GetTraceId(types, station).Item1;

                        // 对比新旧状态，无变化则跳过
                        if (oldStatus.Equals(status))
                            continue;

                        switch (status)
                        {
                            // 校验
                            case "1":
                                typeString = "校验";
                                break;
                            // 入站
                            case "2":
                                typeString = "入站";
                                break;
                            // 出站
                            case "3":
                                typeString = "出站";
                                break;
                            // 信号复位
                            case "4":
                                typeString = "信号复位";
                                break;
                            // 点检
                            case "5":
                                typeString = "点检";
                                break;
                            // 补序
                            case "6":
                                typeString = "补序";
                                break;
                            // 补结束时间
                            case "7":
                                typeString = "结束时间增补";
                                break;
                            default:
                                typeString = "未知";
                                code = status;
                                break;
                        }

                        Tools.ProduceLogText($"[{station}]触发[{typeString}]状态，关联码[{code}]");

                        if (status.Equals("1") || status.Equals("2") || status.Equals("3") || status.Equals("5") || status.Equals("6") || status.Equals("7"))
                        {
                            if (string.IsNullOrEmpty(code))
                            {
                                returnByte = 3;

                                continue;
                            }
                        }

                        switch (status)
                        {
                            // 校验
                            case "1":
                                // 执行校验
                                returnByte = verificationService.CodeVerification(station, code);

                                // 校验成功 执行备份
                                orderDao.BackUpCode(code, station);
                                break;
                            // 入站
                            case "2":
                                returnByte = entranceService.StationEntrance(station, code, operation);
                                break;
                            // 出站
                            case "3":
                                returnByte = exitService.StationExit(station, bytes, code);
                                break;
                            // 点检
                            case "5":
                                returnByte = spotCheakService.StationSpotChackSave(station, orderModel, code);
                                break;
                            // 补序
                            case "6":
                                returnByte = errortype.InsertStation(station, code, orderModel);
                                break;
                            // 补结束时间
                            case "7":
                                returnByte = errortype.InsertEndStation(station, code, orderModel);
                                break;
                        }
                    }
                    finally
                    {
                        // 记录状态与返回值
                        orderModel.StatusDic[station] = (status,returnByte);

                        // 获取对应工位的已读信号
                        int off = MappingTable.GetReadOff(station);

                        // 写入对应的已读信号
                        orderModel.plc.Write(DataType.DataBlock, 5001, off, returnByte);
                    }
                }
            }
            catch (Exception ex)
            {
                Tools.ProduceLogTextError($"{orderModel.PlcName}处理读取完成的信号，将追溯码与订单、节拍等信息绑定失败，原因是：" + ex.Message);
            }
        }

        /// <summary>
        /// 保存工位相关数据
        /// </summary>
        /// <param name="station"></param>
        private void SaveStationData(string station, PlcOrderConfigModel orderModel, Dictionary<object, Type> types)
        {
            try
            {
                // 初始化工序
                List<string> operationList = new List<string>();

                // 获取当前工位的数据偏移量
                Dictionary<string, Tuple<int, int>> stationDataOff = MappingTable.GetDicByName(station);

                char[] chars = station.ToArray();

                StringBuilder sb = new StringBuilder(station);

                for (int i = 0; i < sb.Length; i++)
                {
                    if (sb[i] == 'L' || sb[i] == 'R')
                    {
                        sb.Remove(i, 1); // 删除当前字符
                        i--; // 因为删除字符后，后面的字符会向前移动，所以需要将索引回退
                    }
                }

                string stationResult = sb.ToString();

                // 获取工位对应的数据块
                string operations = MappingTable.GetOperationNo(orderModel.PlcName, stationResult);

                if (operations.Contains(","))
                {
                    string[] strings = operations.Split(',');

                    for (int i = 0; i < strings.Length - 1; i++)
                    {
                        operationList.Add(strings[i]);
                    }
                }
                else
                {
                    operationList.Add(operations);
                }

                if (operationList.Count == 0)
                    return;

                // 获取二维码列表
                (string traceCode, Dictionary<string, int> batchCodes) = GetTraceId(types, station);

                // 存储二维码关联关系
                int id = SaveCode(traceCode, batchCodes, station);

                if (id == 0)
                    return;

                // 存储工位数据
                string result = SaveProcessData(orderModel, station, id, operationList,traceCode);

                // 存储工艺数据
                foreach (var operation in operationList)
                {
                    // 获取保存DB信息的二元组
                    Tuple<int, int> dbTuple = MappingTable.GetBytesOff($"{orderModel.PlcName}-{operation}");

                    byte[] bytes = null;

                    if (dbTuple != null)
                    {
                        // 读取数组信息
                        bytes = orderModel.plc.ReadBytes(DataType.DataBlock, dbTuple.Item1, SystemConstant.ALL_STARTOFF, dbTuple.Item2);
                    }

                    // 读取该工位相关工艺参数
                    ReadOperationData(station, orderModel, bytes, operation, traceCode, result);
                }
            }
            catch (Exception ex)
            {
                Tools.ProduceLogTextError($"{orderModel.PlcName}处理{station}工位过程数据时发生异常，异常信息为{ex.Message}");
            }
        }

        /// <summary>
        /// 存储码关系
        /// </summary>
        /// <param name="traceCode"></param>
        /// <param name="batchCodes"></param>
        /// <exception cref="NotImplementedException"></exception>
        private int SaveCode(string traceCode, Dictionary<string, int> batchCodes, string station)
        {
            try
            {
                // 插入追溯码，获取主键
                int id = orderDao.CodeIsSave(traceCode,station);

                if (batchCodes == null && batchCodes.Count > 0)
                    return id;

                foreach (var batchCode in batchCodes)
                {
                    // 插入批次码，获取主键
                    int batchId = orderDao.BatchCodeIsSave(batchCode.Key);

                    orderDao.CodeAndBatchCodeIsSave(id, batchId, batchCode.Value, station);
                }

                return id;
            }
            catch (Exception ex)
            {
                Tools.ProduceLogTextError($"{station}存储追溯码与批次码关系时异常，原因是{ex.Message}");
            }
            return 0;
        }

        /// <summary>
        /// 获取批次码集合与精追码
        /// </summary>
        /// <param name="types"></param>
        /// <param name="station"></param>
        /// <returns></returns>
        private (string, Dictionary<string, int>) GetTraceId(Dictionary<object, Type> types, string station)
        {
            // 初始化追溯码列表
            string traceCode = string.Empty;

            // 初始化批次码列表
            Dictionary<string, int> batchCodes = new Dictionary<string, int>();

            try
            {
                //获取二维码与批次码
                for (int i = 1; i <= 5; i++)
                {
                    string code = plcDao.GetStringFieldValue(types, SystemConstant.CODE, station + $"_{i}").Trim();

                    Dictionary<string, object> dictionary = plcDao.GetFieldValues(types, SystemConstant.CODE);

                    if (string.IsNullOrEmpty(code))
                        continue;

                    if (i == 1)
                    {
                        traceCode = code;
                    }
                    else
                    {
                        string[] codeArray = code.Split(' ');

                        string batchCode = codeArray[0];

                        int index = int.Parse(codeArray[1]);

                        batchCodes.Add(batchCode, index);
                    }
                }
            }
            catch (Exception ex)
            {
                Tools.ProduceLogTextError($"{station}分解追溯码与批次码失败，原因是{ex.Message}");
            }

            return (traceCode, batchCodes);
        }

        /// <summary>
        /// 存储过点数据信息
        /// </summary>
        /// <param name="orderModel"></param>
        /// <param name="station"></param>
        /// <param name="traceId"></param>
        private string SaveProcessData(PlcOrderConfigModel orderModel, string station, int traceId, List<string> operationList, string traceCode)
        {
            // 获取过程数据数组
            Tuple<int, int> tuple = MappingTable.GetStationOff(station);

            if (tuple == null)
                return "";

            // 读取数组信息
            byte[] bytes = orderModel.plc.ReadBytes(DataType.DataBlock, tuple.Item1, SystemConstant.ALL_STARTOFF, tuple.Item2);

            // 获取工站过程数据
            StationData data = PlcDataUtil.FromBytesToClass(bytes, MappingTable.GetDicByName(station), station);

            if (data != null)
            {
                if (traceId == 0)
                    return "";

                // 获取对应工位的已读信号
                int off = MappingTable.GetReadOff(station);

                if (off == 1000)
                {
                    Tools.ProduceLogText($"无法获取{station}的已读偏移");

                    return "";
                }

                // 插入过程数据
                int processId = orderDao.InsertProcessData(data,station);

                bool isSave = orderDao.ProCessIdSave(traceId, processId, station);

                if (isSave && station.Contains("Op10"))
                {
                    Tools.ProduceLogText("触发工件绑定");

                    // 触发工单绑定
                    BandingOrderIdAndTraceCode(traceCode, station);
                }

                byte result = 2;

                // 写入对应的已读信号
                orderModel.plc.Write(DataType.DataBlock, 5001, (int)off, result);
                // Tools.ProduceLogText($"{orderModel.PlcName}已返回已读信号，DB：5001、偏移量：{off}、返回值：{result}");

                // 写入看板数据表
                if (!data.Result.ToString().Equals("1"))
                {
                    foreach (var operation in operationList)
                    {
                        orderDao.InsertBoardData(data, traceId, operation);
                    }
                }
            }
            else
            {
                Tools.ProduceLogTextError($"{orderModel.PlcName}处理{station}工位过程数据时读取到空值，请检查！");
            }

            return data.Result.ToString();
        }

        /// <summary>
        /// 操作工单绑定追溯码
        /// </summary>
        /// <param name="traceCode"></param>
        /// <param name="station"></param>
        private void BandingOrderIdAndTraceCode(string traceCode, string station)
        {
            List<OrderSingletonModel> orderModels = OrderSingleton.Instance;

            if (orderModels?.Count > 0)
            {
                string materialId = station.EndsWith("L") ? "100401002102" : "100401002103";

                orderModels.Where(o => o.MaterialId == materialId)
                           .ToList()
                           .ForEach(order => orderDao.BandingOrderAndTraceCode(traceCode, order.OrderId));
            }
        }

        /// <summary>
        /// 处理需要从Plc中读取的工艺数据
        /// </summary>
        /// <param name="station"></param>
        /// <param name="plcModel"></param>
        /// <param name="bytes"></param>
        /// <param name="operation"></param>
        /// <param name="code"></param>
        private void ReadOperationData(string station, PlcOrderConfigModel orderModel, byte[] bytes, string operation, string code, string result)
        {
            if (result.Equals("1"))
            {
                result = "OK";
            }
            else
            {
                result = "NG";
            }

            if (!string.IsNullOrEmpty(operation))
            {
                // 根据工艺判断需要从Plc读取的工艺数据
                switch (operation)
                {
                    case "GLUE":
                        GetGlueDataSave(station, bytes, code, operation);
                        break;
                    case "WELD":
                        GetSpotWeldDataSave(station, bytes, code);
                        break;
                    case "POINT":
                        GetPointDataSave(station, bytes, code);
                        break;
                }
            }
        }

        /// <summary>
        /// 读取并保存打刻数据
        /// </summary>
        /// <param name="station"></param>
        /// <param name="bytes"></param>
        /// <param name="code"></param>
        private void GetPointDataSave(string station, byte[] bytes, string code)
        {
            // 实例化数据封装类
            PointData pointData = new PointData()
            {
                Code = code,
                Station = station,
            };

            switch (station)
            {
                case "Op160L_2":

                    pointData.PointCode = Encoding.UTF8.GetString(bytes.Skip(188).Take(64).ToArray()).Replace("\0", "").Trim();

                    break;
                case "Op160R_2":

                    pointData.PointCode = Encoding.UTF8.GetString(bytes.Skip(440).Take(64).ToArray()).Replace("\0", "").Trim();

                    break;
            }

            // 判断打刻数据是否插入
            if (!orderDao.PointDataIsSave(code, station))
                orderDao.PointDataSave(pointData);
        }

        /// <summary>
        /// 读取并保存点焊数据
        /// </summary>
        /// <param name="station"></param>
        /// <param name="bytes"></param>
        /// <param name="code"></param>
        private void GetSpotWeldDataSave(string station, byte[] bytes, string code)
        {
            try
            {
                // 获取点焊偏移量
                Dictionary<string, int> weldData = MappingTable.GetWeldOff(station);

                // 封装数据
                TraceSpotWeld traceSpotWeld = new TraceSpotWeld();

                for (int i = 0; i < weldData["Off"]; i++)
                {
                    int weldTime = S7.Net.Types.Int.FromByteArray(bytes.Skip(weldData["WeldTime"] + 18 * i).Take(2).ToArray());

                    traceSpotWeld.TraceId = code;
                    traceSpotWeld.ErrorCode = S7.Net.Types.Byte.FromByteArray(bytes.Skip(weldData["ErrorCode"] + 18 * i).Take(1).ToArray()).ToString();
                    traceSpotWeld.Num = S7.Net.Types.Byte.FromByteArray(bytes.Skip(weldData["Num"] + 18 * i).Take(1).ToArray()).ToString();
                    traceSpotWeld.ReHotTime = S7.Net.Types.Int.FromByteArray(bytes.Skip(weldData["ReHotTime"] + 18 * i).Take(2).ToArray()).ToString();
                    traceSpotWeld.ReHotA = S7.Net.Types.Int.FromByteArray(bytes.Skip(weldData["ReHotA"] + 18 * i).Take(2).ToArray()).ToString();
                    traceSpotWeld.ReHotV = S7.Net.Types.Int.FromByteArray(bytes.Skip(weldData["ReHotV"] + 18 * i).Take(2).ToArray()).ToString();
                    traceSpotWeld.WeldTime = weldTime.ToString();
                    traceSpotWeld.WeldA = S7.Net.Types.Int.FromByteArray(bytes.Skip(weldData["WeldA"] + 18 * i).Take(2).ToArray()).ToString();
                    traceSpotWeld.WeldV = S7.Net.Types.Int.FromByteArray(bytes.Skip(weldData["WeldV"] + 18 * i).Take(2).ToArray()).ToString();
                    traceSpotWeld.R = S7.Net.Types.Int.FromByteArray(bytes.Skip(weldData["R"] + 18 * i).Take(2).ToArray()).ToString();
                    traceSpotWeld.StatusNo = S7.Net.Types.Byte.FromByteArray(bytes.Skip(weldData["StatusNo"] + 18 * i).Take(1).ToArray()).ToString();
                    traceSpotWeld.Result = S7.Net.Types.Byte.FromByteArray(bytes.Skip(weldData["Result"] + 18 * i).Take(1).ToArray()).ToString();

                    traceSpotWeld.PulseCount = (weldTime / 200).ToString();

                    // 判断点焊数据是否保存
                    if (!orderDao.SpotWeldIsSave(traceSpotWeld))
                        orderDao.SaveWeldSpot(traceSpotWeld);
                }
            }
            catch (Exception ex)
            {
                Tools.ProduceLogTextError($"{station}工位点焊工艺数据保存失败，请检查，原因是：{ex.Message}");
            }
        }

        /// <summary>
        /// 读取并保存涂胶数据
        /// </summary>
        /// <param name="station"></param>
        /// <param name="bytes"></param>
        /// <param name="operation"></param>
        /// <param name="code"></param>
        private void GetGlueDataSave(string station, byte[] bytes, string code, string operation)
        {
            try
            {
                Dictionary<string, int> dic = MappingTable.GetGlueOff(station);

                if (dic != null)
                {
                    for (int i = 0; i < dic["Off"]; i++)
                    {
                        TraceGlue traceGlue = new TraceGlue();

                        traceGlue.TraceId = code;
                        traceGlue.Station = station;
                        traceGlue.RangeNo = S7.Net.Types.Int.FromByteArray(bytes.Skip(dic["RangeNo"] + 34 * i).Take(2).ToArray()).ToString();
                        traceGlue.StartTime = S7.Net.Types.DateTime.FromByteArray(bytes.Skip(dic["StartTime"] + 34 * i).Take(8).ToArray()).ToString();
                        traceGlue.EndTime = S7.Net.Types.DateTime.FromByteArray(bytes.Skip(dic["EndTime"] + 34 * i).Take(8).ToArray()).ToString();
                        traceGlue.Press = S7.Net.Types.Real.FromByteArray(bytes.Skip(dic["Press"] + 34 * i).Take(4).ToArray()).ToString();
                        traceGlue.Speed = S7.Net.Types.Real.FromByteArray(bytes.Skip(dic["Speed"] + 34 * i).Take(4).ToArray()).ToString();
                        traceGlue.Amount = S7.Net.Types.Real.FromByteArray(bytes.Skip(dic["Amount"] + 34 * i).Take(4).ToArray()).ToString();
                        traceGlue.Temperature = S7.Net.Types.Real.FromByteArray(bytes.Skip(dic["Temperature"] + 34 * i).Take(4).ToArray()).ToString();

                        // 判断涂胶数据是否保存
                        if (!orderDao.GlueDataIsSave(code, station, traceGlue.RangeNo))
                            orderDao.GlueDataSave(traceGlue);
                    }
                }
            }
            catch (Exception ex)
            {
                Tools.ProduceLogTextError($"{station}工位涂胶工艺数据保存失败，请检查，原因是：{ex.Message}");
            }
        }
    }
}